﻿// -------------------------------------------------------------------------
//    @FileName         :    NFCityNpcSpawnData.cpp
//    @Author           :    Gao.Yi
//    @Date             :    2025-09-29
//    @Email			:    445267987@qq.com
//    @Module           :    NFCityNpcSpawnData
//    @Desc             :
//
// -------------------------------------------------------------------------

#include "NFCityNpcSpawnData.h"

#include "NFCityNpc.h"
#include "NFCityNpcUtil.h"
#include "NpcBaseDescEx.h"
#include "DescStore/FunctionNpcDesc.h"
#include "DescStore/NpcLocationDesc.h"
#include "DescStore/NpcSpawnDesc.h"
#include "NFComm/NFCore/NFRandom.hpp"

NFCityNpcSpawnData::NFCityNpcSpawnData()
{
    if (EN_OBJ_MODE_INIT == NFShmMgr::Instance()->GetCreateMode())
    {
        CreateInit();
    }
    else
    {
        ResumeInit();
    }
}

NFCityNpcSpawnData::~NFCityNpcSpawnData()
{
}

int NFCityNpcSpawnData::CreateInit()
{
    m_iMapInstID = INVALID_ID;
    m_iSpawnId = INVALID_ID;
    m_iLastSpawnTime = 0;
    m_iInterval = 0;

    m_bEventTrigger = false;
    return 0;
}

int NFCityNpcSpawnData::ResumeInit()
{
    return 0;
}

int NFCityNpcSpawnData::Init(int iMapInstID, int iSpawnId)
{
    m_iMapInstID = iMapInstID;
    m_iSpawnId = iSpawnId;

    auto pstNpcSpawnDesc = NpcSpawnDesc::Instance()->GetDesc(m_iSpawnId);
    CHECK_NULL_WF(0, pstNpcSpawnDesc, "no npc spawn desc for id:{}", m_iSpawnId);

    m_iInterval = pstNpcSpawnDesc->interval;

    auto vecDesc = NpcLocationDesc::Instance()->GetDescByLocationGroupId(pstNpcSpawnDesc->location_group_id);
    CHECK_EXPR(vecDesc.size() > 0 && vecDesc.size() <= MAX_LOCATION_NUM, -1, "invalid location_group_id:{}", pstNpcSpawnDesc->location_group_id);

    m_bEventTrigger = pstNpcSpawnDesc->event_trigger;

    int iSpawnEndTime = 0;
    if (IsInSpawnPeriod(iSpawnEndTime) && !m_bEventTrigger)
    {
        int iRetCode = Spawn(iSpawnEndTime);
        CHECK_RET(iRetCode, "spawn npc failed!");
    }

    return 0;
}

void NFCityNpcSpawnData::Tick()
{
    int iSpawnEndTime = 0;
    if (!m_bEventTrigger && IsInSpawnPeriod(iSpawnEndTime) && IsNeedReflush())
    {
        Spawn(iSpawnEndTime);
    }
}

int NFCityNpcSpawnData::OnRemoveNpc(int iNpcGid, int iLocationId, int iNpcId)
{
    DelNpcGid(iNpcGid);

    m_bLocationIdMap.erase(iLocationId);
    m_bNpcIdMap.erase(iNpcId);

    LOG_DEBUG(0, "spawn id:{} destroy npc", m_iSpawnId);

    return 0;
}

int NFCityNpcSpawnData::RefreshAllNpc(int iLeftTime)
{
    for (auto iter = m_aiNpcGid.begin(); iter != m_aiNpcGid.end(); ++iter)
    {
        NFCityNpc* pNpc = NFCityNpc::GetObjByGlobalId(*iter);
        if (pNpc)
        {
            pNpc->Refresh(iLeftTime);
        }
    }

    return 0;
}

int NFCityNpcSpawnData::Spawn(int iSpawnEndTime)
{
    auto pstNpcSpawnDesc = NpcSpawnDesc::Instance()->GetDesc(m_iSpawnId);
    CHECK_NULL(0, pstNpcSpawnDesc);

    for (int i = 0; i < pstNpcSpawnDesc->reflesh_num && m_aiNpcGid.size() < pstNpcSpawnDesc->limite_num; ++i)
    {
        int iNpcId = RandNpc(pstNpcSpawnDesc);
        if (iNpcId <= 0)
        {
            LOG_ERR(0, -1, "spawn id:{} rand npc id failed!", m_iSpawnId);
            continue;
        }

        auto pNpcDesc = FunctionNpcDesc::Instance()->GetDesc(iNpcId);
        CHECK_NULL_WF(0, pNpcDesc, "invalid npc id:{}", iNpcId);

        if (pNpcDesc->is_static)
        {
            LOG_INFO(0, "npc:{} is static", iNpcId);
            continue;
        }

        int locationId = RandLocation(pstNpcSpawnDesc->location_group_id);
        auto pstNpcLocationDesc = NpcLocationDesc::Instance()->GetDesc(locationId);
        if (pstNpcLocationDesc)
        {
            Proto_Vector3 oPos;
            oPos.x = pstNpcLocationDesc->x_axis;
            oPos.y = pstNpcLocationDesc->y_axis;
            oPos.z = pstNpcLocationDesc->z_axis;

            NFCityNpc* pNpc = NFCityNpcUtil::CreateNpc(m_iMapInstID, iNpcId, oPos, pstNpcLocationDesc->direction, 0);
            if (pNpc)
            {
                pNpc->SetSpawnId(m_iSpawnId, locationId, iSpawnEndTime);
                m_bLocationIdMap.insert(locationId);
                m_bNpcIdMap.insert(iNpcId);

                LOG_DEBUG(0, "spawn id:{} create npc:{} gid:{} at location id:{}", m_iSpawnId, iNpcId, pNpc->GetGlobalId(), locationId);

                //broadcast notice
                AddNpcGid(pNpc->GetGlobalId());
            }
        }
        else
        {
            LOG_ERR(0, -1, "spawn id:{} random location failed!", m_iSpawnId);
        }
    }

    m_iLastSpawnTime = NF_ADJUST_TIMENOW();

    return 0;
}

bool NFCityNpcSpawnData::IsNeedReflush()
{
    if (m_iInterval > 0)
    {
        if (NF_ADJUST_TIMENOW() - m_iLastSpawnTime >= m_iInterval)
        {
            return true;
        }
    }

    return false;
}

bool NFCityNpcSpawnData::IsInSpawnPeriod(int& iSpawnEndTime)
{
    auto pstNpcSpawnDesc = NpcSpawnDesc::Instance()->GetDesc(m_iSpawnId);
    CHECK_NULL_RE_FALSE_WF(0, pstNpcSpawnDesc, "no npc spawn desc for id:{}", m_iSpawnId);

    time_t iTimeNow = NF_ADJUST_TIMENOW();
    if (pstNpcSpawnDesc->utc_start_time > 0 && iTimeNow < pstNpcSpawnDesc->utc_start_time)
    {
        return false;
    }

    if (pstNpcSpawnDesc->utc_end_time > 0 && iTimeNow > pstNpcSpawnDesc->utc_end_time)
    {
        iSpawnEndTime = pstNpcSpawnDesc->utc_end_time;
        return false;
    }

    if (pstNpcSpawnDesc->weekday.size() > 0)
    {
        int iWeekDay = NFTimeUtility::GetWeekDay127(iTimeNow);
        if (std::find(pstNpcSpawnDesc->weekday.begin(), pstNpcSpawnDesc->weekday.end(), iWeekDay) == pstNpcSpawnDesc->weekday.end())
        {
            return false;
        }

        int iStartTime = NFTimeUtility::GetTodayStartTime(iTimeNow) + pstNpcSpawnDesc->utc_start_daytime;
        if (iTimeNow < iStartTime)
        {
            return false;
        }

        int iEndTime = NFTimeUtility::GetTodayStartTime(iTimeNow) + pstNpcSpawnDesc->utc_end_daytime;
        if (iTimeNow > iEndTime)
        {
            return false;
        }

        iSpawnEndTime = iSpawnEndTime > 0 ? std::min(iSpawnEndTime, iEndTime) : iEndTime;
    }
    else
    {
        int iStartTime = NFTimeUtility::GetTodayStartTime(iTimeNow) + pstNpcSpawnDesc->utc_start_daytime;
        if (pstNpcSpawnDesc->utc_start_daytime > 0 && iTimeNow < iStartTime)
        {
            return false;
        }

        int iEndTime = NFTimeUtility::GetTodayStartTime(iTimeNow) + pstNpcSpawnDesc->utc_end_daytime;
        if (pstNpcSpawnDesc->utc_end_daytime > 0)
        {
            if (iTimeNow > iEndTime)
            {
                return false;
            }
            else
            {
                iSpawnEndTime = iSpawnEndTime > 0 ? std::min(iSpawnEndTime, iEndTime) : iEndTime;
            }
        }
    }

    return true;
}

int NFCityNpcSpawnData::RandNpc(const E_NpcSpawn* pstSpawnDesc)
{
    CHECK_NULL(0, pstSpawnDesc);

    if (pstSpawnDesc->npc.size() > 0)
    {
        if (pstSpawnDesc->unique)
        {
            for (int i = 0; i < pstSpawnDesc->npc.size(); ++i)
            {
                if (m_bNpcIdMap.find(pstSpawnDesc->npc[i]) == m_bNpcIdMap.end())
                {
                    return pstSpawnDesc->npc[i];
                }
            }
        }

        int iRandIdx = NFRandInt(0, (int)pstSpawnDesc->npc.size());
        if (iRandIdx >= 0 && iRandIdx < pstSpawnDesc->npc.size())
        {
            return pstSpawnDesc->npc[iRandIdx];
        }
        else
        {
            LOG_ERR(0, -1, "spawn id:{} rand npc idx failed!", m_iSpawnId);
            return INVALID_ID;
        }
    }

    return INVALID_ID;
}

int NFCityNpcSpawnData::RandLocation(int locationGroupId)
{
    auto vecDesc = NpcLocationDesc::Instance()->GetDescByLocationGroupId(locationGroupId);
    CHECK_EXPR(vecDesc.size() > 0, INVALID_ID, "invalid locationGroupId:{}", locationGroupId);

    std::random_shuffle(vecDesc.begin(), vecDesc.end());
    for (int i = 0; i < vecDesc.size(); ++i)
    {
        if (m_bLocationIdMap.find(vecDesc[i]->id) == m_bLocationIdMap.end())
        {
            return vecDesc[i]->id;
        }
    }

    int iIdx = NFRandInt(0, (int)vecDesc.size());
    if (iIdx >= 0 && iIdx < vecDesc.size())
    {
        return vecDesc[iIdx]->id;
    }
    else
    {
        LOG_ERR(0, -1, "spawn id:{} rand location idx failed!", m_iSpawnId);
        return INVALID_ID;
    }

    return INVALID_ID;
}

int NFCityNpcSpawnData::AddNpcGid(int iNpcGid)
{
    if (m_aiNpcGid.full())
    {
        LOG_ERR(0, -1, "spawn id:{} npc gid full!", m_iSpawnId);
        return 0;
    }
    m_aiNpcGid.insert(iNpcGid);
    return 0;
}

int NFCityNpcSpawnData::DelNpcGid(int iNpcGid)
{
    if (m_aiNpcGid.find(iNpcGid) == m_aiNpcGid.end())
    {
        LOG_ERR(0, -1, "spawn id:{} no npc gid:{}", m_iSpawnId, iNpcGid);
        return 0;
    }
    m_aiNpcGid.erase(iNpcGid);
    return 0;
}
